package com.markpim.concurrency.opdr2;

import java.util.ArrayList;
import java.util.concurrent.Semaphore;

public class MeetingRoom {

	private static final int NUMBER_OF_CUSTOMERS = 5;
	private static final int NUMBER_OF_DEVELOPERS = 7;

	private Thread[] customers;
	private Thread[] developers;
	private Thread klaas;

	private ArrayList<Developer> devQueue;
	private ArrayList<Customer> cusQueue;
	private ArrayList<Customer> bugList;

	private Semaphore waitingDevs, enterDevQueue, waitingCustomers, inviteCustomer, meeting, reportBug;

	// private int waitingDevCount;

	/**
	 * The constructor for the MeetingRoom class. Creates all the developers,
	 * the customers and Klaas.
	 */
	public MeetingRoom() {

		System.out.println("room created");

		waitingDevs = new Semaphore(NUMBER_OF_DEVELOPERS);
		enterDevQueue = new Semaphore(1);
		waitingCustomers = new Semaphore(NUMBER_OF_CUSTOMERS);
		inviteCustomer = new Semaphore(1);
		meeting = new Semaphore(1);

		reportBug = new Semaphore(1);
		bugList = new ArrayList<Customer>();

		cusQueue = new ArrayList<Customer>();
		devQueue = new ArrayList<Developer>();

		customers = new Thread[NUMBER_OF_CUSTOMERS];
		for (int i = 0; i < customers.length; i++) {
			customers[i] = new Customer(i);
			customers[i].start();
		}

		developers = new Thread[NUMBER_OF_DEVELOPERS];
		for (int i = 0; i < developers.length; i++) {
			developers[i] = new Developer(i);
			developers[i].start();
		}

		klaas = new Klaas();
		klaas.start();

	}

	/**
	 * The developer
	 * 
	 * @author Pim
	 * @author Mark
	 * 
	 */
	private class Developer extends Thread {
		private int number;

		/**
		 * @param number
		 *            A number to identify the developer
		 */
		public Developer(int number) {
			this.number = number;
		}

		/**
		 * Lets the developer work for a while and then he readies himself for a
		 * meeting
		 */
		@Override
		public void run() {
			while (true) {
				try {
					work();

					if (meeting.tryAcquire(0)) {
						waitingDevs.acquire();
						enterDevQueue.acquire(); // kijk of er al iemand de rij
													// wachtrij in of uit gaat
						devQueue.add(this);
						enterDevQueue.release();
					}
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
		}

		/**
		 * Lets the thread sleep for a random amount of time (sleeping =
		 * working)
		 */
		private void work() {
			try {
				Thread.sleep((long) (Math.random() * 10000));
			} catch (InterruptedException e) {

			}
		}

	}

	/**
	 * The customer
	 * 
	 * @author Pim
	 * @author Mark
	 * 
	 */
	private class Customer extends Thread {
		private int number;

		/**
		 * 
		 * @param number
		 *            A number to identify the customer
		 */
		public Customer(int number) {
			this.number = number;
		}

		@Override
		public void run() {
			while (true) {
				beCustomer(); // being a customer means perhaps finding a bug
			}
		}

		/**
		 * The main action for the customer is to just be a customer, This will
		 * let the thread sleep for a random amount of time. While being a
		 * customer there is also a chance to find the bug in the program with
		 * the method 'lookForBug(int chance)'
		 */
		private void beCustomer() {
			try {
				lookForBug(5);
				Thread.sleep((long) (Math.random() * 1000));
			} catch (InterruptedException e) {

			}
		}

		/**
		 * If a bug occurs, the customer will be added to the 'bugList' which
		 * will be looked at by the class 'Klaas'
		 * 
		 * @param chancePercent
		 *            the chance that a bug will occur
		 */
		private void lookForBug(int chancePercent) {
			if (Math.random() * 100 > (100 - chancePercent)) {
				try {
					// a customer can only report 1 bug. Then he should wait on
					// being invited to the company
					waitingCustomers.acquire();
					System.out.println("BUG FOUND!!!11");
					reportBug.acquire(); // for no problems with the list
					bugList.add(this);
					reportBug.release();
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
		}

		/**
		 * Lets the thread sleep a random amount of time before adding the
		 * customer to the 'cusqueue' (a queue dictating how many customers are
		 * waiting at the company)
		 */
		public void travelToCompany() {
			try {
				Thread.sleep((long) (Math.random() * 1000));
				inviteCustomer.acquire();
				cusQueue.add(this);
				inviteCustomer.release();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @author Mark
	 * @author Pim
	 * 
	 */
	private class Klaas extends Thread {

		/**
		 * The main method of the class Klaas. First, the method checks if there
		 * is a bug, if so, the reporter of the bug is invited to the company
		 * 
		 * Second, the method checks if there are any customers waiting at the
		 * company, if so, the method will check if there is a developer waiting
		 * if there is a dev, the meeting will start. If there is not a dev
		 * available for a meeting then Klaas will take a nap and the method
		 * will run again.
		 * 
		 * Third, the method will check if there are more then 3 developers
		 * ready for a meeting, if this is true, and there are no clients
		 * waiting for a meeting a 'developer meeting' will be started.
		 */
		@Override
		public void run() {
			while (true) {
				ArrayList<Developer> meetingDevs = new ArrayList<Developer>();
				ArrayList<Customer> meetingCusts = new ArrayList<Customer>();

				// kijk eerst of er bugs zijn en nodigt dan (misschien) een
				// klant
				// uit
				if (bugList.size() > 0) {
					// er is een bug
					handleBug();
				}
				if (cusQueue.size() > 0) {
					// System.out.println("there is a customer waiting for a meeting");
					if (devQueue.size() > 0) { // there is a dev waiting for a
												// meeting so invite him to the
												// customer meeting
						try {
							enterDevQueue.acquire();
							meetingDevs.add(devQueue.get(0)); // just get the
																// first dev in
																// line, doesn't
																// matter..
							for (int i = 0; i < devQueue.size(); i++) { // now
																		// release
																		// the
																		// other
																		// devs
								waitingDevs.release();
							}
							devQueue.clear(); // the other dev's dont have to
												// wait for the meeting anymore
							enterDevQueue.release();

							// now add all the customers that are waiting for
							// the meeting
							inviteCustomer.acquire();
							for (int i = 0; i < cusQueue.size(); i++) {
								meetingCusts.add(cusQueue.get(i));
								waitingCustomers.release();
							}
							cusQueue.clear();
							inviteCustomer.release();
							// now the meeting can start
							doMeeting(meetingDevs, meetingCusts);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					} else { // stil no dev reported for a meeting so klaas is
								// going to take a nap
						dutjeDoen();
					}
				} else if (devQueue.size() >= 3) { // this is an else if so no
													// meetings can be started
													// with only dev's when
													// there's a customer
													// waiting

					try {
						enterDevQueue.acquire(); // so no others can join the
													// queue while doing this
						for (int i = 0; i < devQueue.size(); i++) {
							if (i < 3) {
								meetingDevs.add(devQueue.get(i));
							}
							waitingDevs.release();
						}
						devQueue.clear();

						meeting.acquire();
						enterDevQueue.release();
						doMeeting(meetingDevs, new ArrayList<Customer>());
					} catch (InterruptedException e1) {
						Thread.currentThread().interrupt();
					}
				} else {
					dutjeDoen();
				}
			}
		}

		/**
		 * If there is a bug in the buglist the customer who reported the bug is
		 * invited to the company with the function 'travelToCompany()'
		 */
		private void handleBug() {
			System.out.println("bug is being handled");
			// dus, er is een bug, nu moet de klant worden uitgenodigd om naar
			// het bedrijf te komen.
			try {
				reportBug.acquire();
				for (Customer c : bugList) {
					c.travelToCompany();
				}
				bugList.clear();
				reportBug.release();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		/**
		 * Function for klaas to 'take a nap' Lets the thread sleep for a random
		 * amount of time
		 */
		private void dutjeDoen() {
			try {
				Thread.sleep((long) (Math.random() * 1000));
			} catch (InterruptedException e) {

			}
		}

		/**
		 * This starts a meeting and then waits a random amount of time before
		 * the meeting is ended
		 * 
		 * @param devs
		 *            - a list of dev's who participate in the meeting
		 * @param clients
		 *            - a list of customers who participate in the meeting
		 */
		private void doMeeting(ArrayList<Developer> devs, ArrayList<Customer> clients) {
			System.out.println("starting meeting with " + devs.size() + " developers and " + clients.size() + " clients");
			try {
				Thread.sleep((long) (Math.random() * 1000));
				meeting.release();
			} catch (InterruptedException e) {

			}
		}
	}
}
